Este material consiste na resolução das atividades da disciplina Processamento Digital de Imagens lecionada por Agostinho Brito Jr. Todas as atividades constam em http://agostinhobritojr.github.io/tutoriais/pdi/. Além disso, a resolução e códigos de todas essas atividades se encontram no meu GitHub: https://github.com/vanessadants. Para voltar para o início acessar https://vanessadants.github.io/

1. Unidade 1

1.1. Manipulando pixels em uma imagem

1.2. Negativo em retângulo

  • Utilizando o programa exemplos/pixels.cpp como referência, implemente um programa regions.cpp. Esse programa deverá solicitar ao usuário as coordenadas de dois pontos P1 e P2 localizados dentro dos limites do tamanho da imagem e exibir que lhe for fornecida. Entretanto, a região definida pelo retângulo de vértices opostos definidos pelos pontos P1 e P2 será exibida com o negativo da imagem na região correspondente. O efeito é ilustrado na Figura Regiões.

    Para resolver essa questão implementamos o código abaixo:
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int, char**){
  Mat image;//matriz de pixels da imagem

  int altura, largura;// altura e largura da imagem
  Vec2i P1, P2; //pontos que o usuário vai informar e servirão para delimitar o retângulo


  image= imread("vanessa.png",CV_LOAD_IMAGE_GRAYSCALE); //carregar a imagem 780 x 1028

  if(!image.data)
    cout << "nao abriu vanessa.png" << endl;

  namedWindow("janela",WINDOW_AUTOSIZE);

  altura=image.rows;
  largura=image.cols;

  //pedir os pontos ao usuário (ponto Pi(x,y))
  do{
    cout<<"Insira as coordenadas do ponto P1 (x,y):\n";
    cin>>P1[0]>>P1[1];

  }while(P1[0]<0 || P1[0]>=altura || P1[1]<0 || P1[1]>=largura);

  do{
    cout<<"Insira as coordenadas do ponto P2 (x,y):\n";
    cin>>P2[0]>>P2[1];

  }while(P2[0]<0 || P2[0]>=altura || P2[1]<0 || P2[1]>=largura);


  //exibir a imagem antes de qualquer modificação
  imshow("janela", image);
  waitKey();


  //Fazer a região definida por esses pontos ficar em negativo
  int li,lf,ci,cf; //servirá para limitar o retângulo
  li=(P1[0]<=P2[0]?P1[0]:P2[0]);
  lf=(P1[0]>P2[0]?P1[0]:P2[0]);

  ci=(P1[1]<=P2[1]?P1[1]:P2[1]);
  cf=(P1[1]>P2[1]?P1[1]:P2[1]);


  for(int i=li;i<lf;i++){
    for(int j=ci;j<cf;j++){
      image.at<uchar>(i,j)=255-image.at<uchar>(i,j); //negativo da imagem
    }
  }

  //imagem com a região retangular definida por P1 e P2 negativa
  imshow("janela", image);
  waitKey();

  return 0;
}
Conforme podemos perceber o código recebe 2 pontos do usuário e para o retângulo formado naquela região cada pixel tem seu valor modificado para 255 menos o valor do pixel, assim a cor fica em negativo.
vanessa
Figure 1. imagem de entrada
2 1 terminal
Figure 2. terminal
2 imagemAntes
Figure 3. imagem de entrada aberta em tons de cinza
2 1 imagemDepois
Figure 4. imagem de saída invertida as cores do retângulo

1.3. Troca de Regiões

  • Utilizando o programa exemplos/pixels.cpp como referência, implemente um programa trocaregioes.cpp. Seu programa deverá trocar os quadrantes em diagonal na imagem. Explore o uso da classe Mat e seus construtores para criar as regiões que serão trocadas. O efeito é ilustrado na Figura Troca de regiões.

    Para resolver essa questão implementamos o código abaixo:
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int, char**){
  Mat image;//matriz de pixels da imagem
  uchar aux;
  int altura, largura;// altura e largura da imagem



  image= imread("vanessa.png",CV_LOAD_IMAGE_GRAYSCALE);

  if(!image.data)
    cout << "nao abriu vanessa.png" << endl;

  namedWindow("janela",WINDOW_AUTOSIZE);

  altura=image.rows;
  largura=image.cols;


  //exibir a imagem antes de qualquer modificação
  imshow("janela", image);
  waitKey();



  //para a primeira metade (i<altura/2 && j<largura/2) ||(i>=altura/2 && j>=largura/2)
  //para inverter horizontalmente a coluna 0 recebe a col largura/2, a 1 recebe largura/2+1 e vice versa
  //para inverter verticalmente a linha 0 recebe a lin altura/2, a 1 recebe altura/2+1 e vice versa

  for(int i=0;i<altura/2;i++){
    for(int j=0;j<largura/2;j++){
      aux=image.at<uchar>(i,j);
      image.at<uchar>(i,j)=image.at<uchar>(i+altura/2,j+largura/2);
      image.at<uchar>(i+altura/2,j+largura/2)=aux;

    }
  }

  //para a segunda metade (i<altura/2 && j>=largura/2) || (i>=altura/2 && j<largura/2)
  //para inverter horizontalmente a coluna 0 recebe a col largura/2, a 1 recebe largura/2+1 e vice versa
  //para inverter verticalmente a linha altura/2 recebe a lin 0, a altura/2+1 recebe a lin 1 e vice versa


  for(int i=altura/2;i<altura;i++){
    for(int j=0;j<largura/2;j++){
      aux=image.at<uchar>(i,j);
      image.at<uchar>(i,j)=image.at<uchar>(i-altura/2,j+largura/2);
      image.at<uchar>(i-altura/2,j+largura/2)=aux;

    }
  }

  //imagem invertida diagonalmente
  imshow("janela", image);
  waitKey();

  return 0;
}
Utilizamos como imagem de entrada a mesma da tarefa anterior. Para trocar as regiões, entenderemos a imagem com 4 quadrantes:
quadrantes
Figure 5. Quadrantes
Para trocar os quadrantes entre si identificamos 2 tipos de troca, quadrante 1 e 4 e outra situação entre o quadrante 2 e 3, vide o código mostrado acima.
As saídas do código foram:
2 2 terminal
Figure 6. terminal
2 imagemAntes
Figure 7. imagem de entrada aberta em tons de cinza
2 2 imagemDepois
Figure 8. imagem de saída com os quadrantes trocados

1.4. Preenchendo regiões

1.5. Labeling em mais de 255 elementos

  • Observando-se o programa labeling.cpp como exemplo, é possível verificar que caso existam mais de 255 objetos na cena, o processo de rotulação poderá ficar comprometido. Identifique a situação em que isso ocorre e proponha uma solução para este problema.

    O código labeling.png utiliza o número do objeto encontrado como o tom de preenchimento daquele objeto, de modo que no fim o objeto terá o valor máximo de nobjects. Desse modo,se houvessem mais de 255 objetos na cena, não poderíamos executar o floodFill utilizando o nobjects, pois trabalhamos com apenas 256 tons de cinza (de 0 a 255), bem como o fato de que o elemento 255 seria pintado com sua própria cor 255, o que é reduntante e produz um erro na contagem (já que um objeto normalmente não é formado por apenas um pixel, ele contaria o mesmo objeto várias vezes).
    Uma solução para este problema seria passar a usar uma imagem colorida, com componentes RGB indo de 0 até 255, aumentariamos o valor máximo da contagem.

1.6. Contando elementos de uma Imagem

  • Aprimore o algoritmo de contagem apresentado para identificar regiões com ou sem buracos internos que existam na cena. Assuma que objetos com mais de um buraco podem existir. Inclua suporte no seu algoritmo para não contar bolhas que tocam as bordas da imagem. Não se pode presumir, a priori, que elas tenham buracos ou não.

    Para resolver essa questão implementamos o código abaixo:
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

bool temBolha(Mat image,int i,int j){

  int l,c;
  CvPoint p;
  Mat aux=image.clone(); //não queremos sobrescrescer a imagem principal

  p.x=0;
  p.y=0;
  floodFill(aux,p,255); //pintamos o fundo de branco

  while(aux.at<uchar>(i,j)!=255){
    //expandir em 8 direções
    //cima
    l=i-1;
    c=j;
    while(aux.at<uchar>(l,c)!=255){
      if(aux.at<uchar>(l,c)==0) //achamos uma região preta
        return true;
      l--;
    }
    //baixo
    l=i+1;
    c=j;
    while(aux.at<uchar>(l,c)!=255){
      if(aux.at<uchar>(l,c)==0) //achamos uma região preta
        return true;
      l++;
    }
    //esquerda
    l=i;
    c=j-1;
    while(aux.at<uchar>(l,c)!=255){
      if(aux.at<uchar>(l,c)==0) //achamos uma região preta
        return true;
      c--;
    }
    //direita
    l=i;
    c=j+1;
    while(aux.at<uchar>(l,c)!=255){
      if(aux.at<uchar>(l,c)==0) //achamos uma região preta
        return true;
      c++;
    }

    //lateral superior esquerda
    l=i-1;
    c=j-1;
    while(aux.at<uchar>(l,c)!=255){
      if(aux.at<uchar>(l,c)==0) //achamos uma região preta
        return true;
      l--;
      c--;
    }
    //lateral superior direita
    l=i-1;
    c=j+1;
    while(aux.at<uchar>(l,c)!=255){
      if(aux.at<uchar>(l,c)==0) //achamos uma região preta
        return true;
      l--;
      c++;
    }
    //lateral inferior esquerda
    l=i+1;
    c=j-1;
    while(aux.at<uchar>(l,c)!=255){
      if(aux.at<uchar>(l,c)==0) //achamos uma região preta
        return true;
      l++;
      c--;
    }
    //lateral inferior direita
    l=i+1;
    c=j+1;
    while(aux.at<uchar>(l,c)!=255){
      if(aux.at<uchar>(l,c)==0) //achamos uma região preta
        return true;
      l++;
      c++;
    }
    i++;
    j++;
  }

  return false;

}


int main(int argc, char** argv){
  Mat image;
  int width, height;
  int nobjects, nbolhas;
  int i,j;

  CvPoint p;
  image = imread(argv[1],CV_LOAD_IMAGE_GRAYSCALE);

  if(!image.data){
    cout << "imagem nao carregou corretamente\n";
    return(-1);
  }
  width=image.size().width;
  height=image.size().height;


  //despreza bolhas existentes na borda da imagem
  p.x=0;
  p.y=0;

  // busca objetos com buracos presentes
  for(i=0; i<height; i++){
    if(image.at<uchar>(i,0) == 255){
      p.x=0;
      p.y=i;
      floodFill(image,p,0);
    }
    if(image.at<uchar>(i,width-1) == 255){
      p.x=width-1;
      p.y=i;
      floodFill(image,p,0);
    }
  }

  for(j=0; j<width; j++){
    if(image.at<uchar>(0,j) == 255){
      p.x=j;
      p.y=0;
      floodFill(image,p,0);
    }
    if(image.at<uchar>(height-1,j) == 255){
      p.x=j;
      p.y=height-1;
      floodFill(image,p,0);
    }
  }


  // busca objetos com buracos presentes,lembrar de desconsiderar mais de 1 buraco

  p.x=0;
  p.y=0;

  nobjects=0;
  nbolhas=0;

  for(i=0; i<height; i++){
    for(j=0; j<width; j++){
      if(image.at<uchar>(i,j) == 255){
        // achou um objeto
        nobjects++;
        p.x=j;
        p.y=i;
        floodFill(image,p,nobjects);
        //verificar se tem ao menos 1 bolha
        if(temBolha(image,i,j)){
          nbolhas++;
        }
      }
    }

  }//ao fim teremos nobjets presentes, bem como o número de objetos com ao menos 1 bolha

  cout<<"Temos "<<nobjects<<", dos quais "<<nbolhas<<" possuem ao menos 1 bolha.\n";

  //exibir e salvar os resultados
  imshow("image", image);
  imwrite("labeling.png", image);
  waitKey();
  return 0;
}
Esse programa é uma modificação do código labeling.cpp descrito nas atividades da disciplina. A mudança está no fato de que, para as regiões contabilizadas com o uso do floodfill, é feita uma análise para determinar se a região é ou não uma bolha.
bolhas
Figure 9. imagem de entrada
3 2 terminal
Figure 10. terminal
3 2 imagemDepois
Figure 11. imagem de entrada depois de contabilizados os objetos
Podemos perceber que esse código trata a situação de que uma bolha pode ter mais de 1 buraco, mas ainda ser a mesma bolha:
bolhasModificado
Figure 12. imagem de entrada modificada para apresentar bolhas com mais de 1 buraco
3 2 terminalVariasBolhas
Figure 13. terminal
3 2 imagemVariasBolhas
Figure 14. imagem de entrada depois de contabilizados os objetos
Percebe que o número de bolhas permanece 7, o que é o esperado.

1.7. Manipulação de histogramas

1.8. Equalização de histograma

  • Utilizando o programa exemplos/histogram.cpp como referência, implemente um programa equalize.cpp. Este deverá, para cada imagem capturada, realizar a equalização do histogram antes de exibir a imagem. Teste sua implementação apontando a câmera para ambientes com iluminações variadas e observando o efeito gerado. Assuma que as imagens processadas serão em tons de cinza.

    Para resolver essa questão implementamos o código abaixo:
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int argc, char** argv){
  Mat image;
  int width, height;
  VideoCapture cap;
  vector<Mat> planes;
  Mat histR, histG, histB;
  int nbins = 64;
  float range[] = {0, 256};
  const float *histrange = { range };
  bool uniform = true;
  bool acummulate = false;

  cap.open(0);

  if(!cap.isOpened()){
    cout << "cameras indisponiveis";
    return -1;
  }

  width  = cap.get(CV_CAP_PROP_FRAME_WIDTH);
  height = cap.get(CV_CAP_PROP_FRAME_HEIGHT);

  cout << "largura = " << width << endl;
  cout << "altura  = " << height << endl;

  int histw = nbins, histh = nbins/2;
  Mat histImgR(histh, histw, CV_8UC3, Scalar(0,0,0));
  Mat histImgG(histh, histw, CV_8UC3, Scalar(0,0,0));
  Mat histImgB(histh, histw, CV_8UC3, Scalar(0,0,0));

  while(1){
    cap >> image;
    split (image, planes);


    equalizeHist(planes[0],planes[0]);
    equalizeHist(planes[1],planes[1]);
    equalizeHist(planes[2],planes[2]);


    merge(planes,image);

    calcHist(&planes[0], 1, 0, Mat(), histR, 1,
             &nbins, &histrange,
             uniform, acummulate);
    calcHist(&planes[1], 1, 0, Mat(), histG, 1,
             &nbins, &histrange,
             uniform, acummulate);
    calcHist(&planes[2], 1, 0, Mat(), histB, 1,
             &nbins, &histrange,
             uniform, acummulate);

    normalize(histR, histR, 0, histImgR.rows, NORM_MINMAX, -1, Mat());
    normalize(histG, histG, 0, histImgG.rows, NORM_MINMAX, -1, Mat());
    normalize(histB, histB, 0, histImgB.rows, NORM_MINMAX, -1, Mat());

    histImgR.setTo(Scalar(0));
    histImgG.setTo(Scalar(0));
    histImgB.setTo(Scalar(0));

    for(int i=0; i<nbins; i++){
      line(histImgR,
           Point(i, histh),
           Point(i, histh-cvRound(histR.at<float>(i))),
           Scalar(0, 0, 255), 1, 8, 0);
      line(histImgG,
           Point(i, histh),
           Point(i, histh-cvRound(histG.at<float>(i))),
           Scalar(0, 255, 0), 1, 8, 0);
      line(histImgB,
           Point(i, histh),
           Point(i, histh-cvRound(histB.at<float>(i))),
           Scalar(255, 0, 0), 1, 8, 0);
    }
    histImgR.copyTo(image(Rect(0, 0       ,nbins, histh)));
    histImgG.copyTo(image(Rect(0, histh   ,nbins, histh)));
    histImgB.copyTo(image(Rect(0, 2*histh ,nbins, histh)));
    imshow("image", image);
    if(waitKey(30) >= 0)
      break;


  }
  return 0;
}
Esse programa é uma modificação do código histogram.cpp descrito nas atividades da disciplina. A mudança está no fato de que, para cada histograma foi feita uma equalização.
4 1Nequalizada
Figure 15. imagem não equalizada
4 1Equalizada
Figure 16. imagem equalizada
Foi feito também um vídeo comparativo:

1.9. Detector de Movimento

  • Utilizando o programa exemplos/histogram.cpp como referência, implemente um programa motiondetector.cpp. Este deverá continuamente calcular o histograma da imagem (apenas uma componente de cor é suficiente) e compará-lo com o último histograma calculado. Quando a diferença entre estes ultrapassar um limiar pré-estabelecido, ative um alarme. Utilize uma função de comparação que julgar conveniente.

    Para resolver essa questão implementamos o código abaixo:
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int argc, char** argv){
  Mat image;
  int width, height;
  VideoCapture cap;
  vector<Mat> planes;
  Mat histR,histOldR;
  int nbins = 64;
  float range[] = {0, 256};
  const float *histrange = { range };
  bool uniform = true;
  bool acummulate = false;

  cap.open(0);

  if(!cap.isOpened()){
    cout << "cameras indisponiveis";
    return -1;
  }

  width  = cap.get(CV_CAP_PROP_FRAME_WIDTH);
  height = cap.get(CV_CAP_PROP_FRAME_HEIGHT);

  cout << "largura = " << width << endl;
  cout << "altura  = " << height << endl;

  int histw = nbins, histh = nbins/2;
  Mat histImgR(histh, histw, CV_8UC3, Scalar(0,0,0));

  cap >> image;

  split (image, planes);
  calcHist(&planes[0], 1, 0, Mat(), histR, 1,
          &nbins, &histrange,
          uniform, acummulate);

  normalize(histR, histR, 0, histImgR.rows, NORM_MINMAX, -1, Mat());

  histImgR.setTo(Scalar(0));

  for(int i=0; i<nbins; i++){
    line(histImgR,
         Point(i, histh),
         Point(i, histh-cvRound(histR.at<float>(i))),
         Scalar(0, 0, 255), 1, 8, 0);
  }
  histImgR.copyTo(image(Rect(0, 0 ,nbins, histh)));

  imshow("image", image);

  while(1){

    cap >> image;

    split (image, planes);

    histOldR=histR.clone();

    calcHist(&planes[0], 1, 0, Mat(), histR, 1,
            &nbins, &histrange,
            uniform, acummulate);

    normalize(histR, histR, 0, histImgR.rows, NORM_MINMAX, -1, Mat());

    histImgR.setTo(Scalar(0));

    for(int i=0; i<nbins; i++){
      line(histImgR,
           Point(i, histh),
           Point(i, histh-cvRound(histR.at<float>(i))),
           Scalar(0, 0, 255), 1, 8, 0);
    }
    histImgR.copyTo(image(Rect(0, 0 ,nbins, histh)));

    double aux = compareHist(histR,histOldR,CV_COMP_CORREL);

    if(aux<=0.99){
      putText(image, "Motion Detected", cvPoint(image.cols/2 - 50, 40), FONT_HERSHEY_COMPLEX_SMALL, 1, Scalar(0, 0, 255), 2);
    }

    imshow("image", image);

    if(waitKey(30) >= 0) break;
  }
  return 0;
}
Esse programa é uma modificação do código histogram.cpp descrito nas atividades da disciplina. Ao invés de calcularmos os histogramas RGB, calculamos apenas o histR. Feito isso, criamos uma variável do tipo Mat que recebe o histR anterior. Por fim, comparamos esses 2 histogramas pela função aux=compareHist(histR,histOldR,CV_COMP_CORREL) e se aux<=0.99 (valor arbitrário) disparamos um "alarme". Veja o vídeo mostrando o funcionamento do código:

1.10. Filtragem no domínio espacial I

===Laplaciano do Gaussiano

  • Utilizando o programa exemplos/filtroespacial.cpp como referência, implemente um programa laplgauss.cpp. O programa deverá acrescentar mais uma funcionalidade ao exemplo fornecido, permitindo que seja calculado o laplaciano do gaussiano das imagens capturadas. Compare o resultado desse filtro com a simples aplicação do filtro laplaciano.

    Para resolver essa questão implementamos o código abaixo:
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

void printmask(Mat &m){
  for(int i=0; i<m.size().height; i++){
    for(int j=0; j<m.size().width; j++){
      cout << m.at<float>(i,j) << ",";
    }
    cout << endl;
  }
}

void menu(){
  cout << "\npressione a tecla para ativar o filtro: \n"
        "a - calcular modulo\n"
    "m - media\n"
    "g - gauss\n"
    "v - vertical\n"
        "h - horizontal\n"
    "l - laplaciano\n"
    "z - laplaciano do gaussiano\n"
        "esc - sair\n";
}

int main(int argvc, char** argv){
  VideoCapture video;
  float media[] = {1,1,1,
                                   1,1,1,
                                   1,1,1};
  float gauss[] = {1,2,1,
                                   2,4,2,
                                   1,2,1};
  float horizontal[]={-1,0,1,
                                          -2,0,2,
                                          -1,0,1};
  float vertical[]={-1,-2,-1,
                                        0,0,0,
                                        1,2,1};
  float laplacian[]={0,-1,0,
                                         -1,4,-1,
                                         0,-1,0};

  float laplacianGauss[]=  {0,0,1,0,0,
                0,1,2,1,0,
                1,2,-16,2,1,
                0,1,2,1,0,
                0,0,1,0,0};

  Mat cap, frame, frame32f, frameFiltered;
  Mat mask(3,3,CV_32F), mask1;
  Mat result, result1;
  double width, height, min, max;
  int absolut;
  char key;

  video.open(0);
  if(!video.isOpened())
    return -1;
  width=video.get(CV_CAP_PROP_FRAME_WIDTH);
  height=video.get(CV_CAP_PROP_FRAME_HEIGHT);
  std::cout << "largura=" << width << "\n";;
  std::cout << "altura =" << height<< "\n";;

  namedWindow("filtroespacial",1);

  mask = Mat(3, 3, CV_32F, media);
  scaleAdd(mask, 1/9.0, Mat::zeros(3,3,CV_32F), mask1);
  swap(mask, mask1);
  absolut=1; // calcs abs of the image

  menu();
  while(1){
    video >> cap;
    cvtColor(cap, frame, CV_BGR2GRAY);
    flip(frame, frame, 1);
    imshow("original", frame);
    frame.convertTo(frame32f, CV_32F);
    filter2D(frame32f, frameFiltered, frame32f.depth(), mask, Point(1,1), 0);
    if(absolut){
      frameFiltered=abs(frameFiltered);
    }
    frameFiltered.convertTo(result, CV_8U);
    imshow("filtroespacial", result);
    key = (char) waitKey(10);
    if( key == 27 ) break; // esc pressed!
    switch(key){
    case 'a':
          menu();
      absolut=!absolut;
      break;
    case 'm':
          menu();
      mask = Mat(3, 3, CV_32F, media);
      scaleAdd(mask, 1/9.0, Mat::zeros(3,3,CV_32F), mask1);
      mask = mask1;
      printmask(mask);
      break;
    case 'g':
          menu();
      mask = Mat(3, 3, CV_32F, gauss);
      scaleAdd(mask, 1/16.0, Mat::zeros(3,3,CV_32F), mask1);
      mask = mask1;
      printmask(mask);
      break;
    case 'h':
          menu();
      mask = Mat(3, 3, CV_32F, horizontal);
      printmask(mask);
      break;
    case 'v':
          menu();
      mask = Mat(3, 3, CV_32F, vertical);
      printmask(mask);
      break;
    case 'l':
          menu();
      mask = Mat(3, 3, CV_32F, laplacian);
      printmask(mask);
      break;
    case 'z':
    menu();
      mask = Mat(5, 5, CV_32F, laplacianGauss);
      printmask(mask);
      break;

    default:
      break;
    }
  }
  return 0;
}
Esse programa é uma modificação do código filtroespacial.cpp descrito nas atividades da disciplina. Foi feita um filtro chamado laplacianGauss e esse filtro corresponde ao filtro laplaciano do gaussiano.
5 1ApenasLap
Figure 17. imagem de apenas o filtro laplaciano aplicado
5 1LapDoGauss
Figure 18. imagem o filtro aplicado foi o laplaciano do gaussiano
Comparando um com o outro, notamos que no laplaciano do gaussiano as bordas aparecem mais evidenciadas. No entanto, há aumento de ruído em relação à imagem apenas do laplaciano.

1.11. Filtragem no domínio espacial II

1.12. Tiltshift

  • Utilizando o programa exemplos/addweighted.cpp como referência, implemente um programa tiltshift.cpp. Três ajustes deverão ser providos na tela da interface:

  • um ajuste para regular a altura da região central que entrará em foco;

  • um ajuste para regular a força de decaimento da região borrada; *um ajuste para regular a posição vertical do centro da região que entrará em foco. Finalizado o programa, a imagem produzida deverá ser salva em arquivo.

    Para resolver essa questão implementamos o código abaixo:
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;



double d=0;
int l1=0,l2=0,h=0;
int width,height;
int centro=0;

int d_slider = 0;
int d_slider_max = 100;

int center_slider = 0;
int center_slider_max = 100;

int height_slider = 0;
int height_slider_max = 100;



Mat image;
Mat image_blurred;
Mat ganhoImage;
Mat ganhoImage_blurred;
Mat imageTiltShift;

char TrackbarName[50];

void makeBlurredImage(){
  Mat result, result1;
  int absolut=1;
  image.copyTo(result);

  float media[] = {1,1,1,1,1,1,1,1,1,
           1,1,1,1,1,1,1,1,1,
           1,1,1,1,1,1,1,1,1,
           1,1,1,1,1,1,1,1,1,
           1,1,1,1,1,1,1,1,1,
           1,1,1,1,1,1,1,1,1,
           1,1,1,1,1,1,1,1,1,
           1,1,1,1,1,1,1,1,1,
           1,1,1,1,1,1,1,1,1};
  Mat mask(9,9,CV_32F,media),mask1;

  mask = Mat(9, 9, CV_32F, media);
  scaleAdd(mask, 1/81.0, Mat::zeros(9,9,CV_32F), mask1);
  mask = mask1;

  result.convertTo(result1, CV_32F);
  filter2D(result1, image_blurred, result1.depth(), mask, Point(1,1), 0);
  if(absolut){
      image_blurred=abs(image_blurred);
  }
  image_blurred.convertTo(image_blurred, CV_8U);


}

void blend(){
  int i,j;
  image.copyTo(ganhoImage);
  image.copyTo(ganhoImage_blurred);
  image.copyTo(imageTiltShift);

  //ganho da imagem de entrada
  for(i=0; i<height; i++){
    for(j=0; j<width; j++){
      ganhoImage.at<uchar>(i,j)= 255*(1/2.0)*(tanh((i-l1)/(d*1.0))-tanh((i-l2)/(d*1.0)));
    }
  }

  //ganho da imagem borrada
  for(i=0; i<height; i++){
    for(j=0; j<width; j++){
      ganhoImage_blurred.at<uchar>(i,j)= 255- ganhoImage.at<uchar>(i,j);
    }
  }

  //gerar imagem final como combinação
  for(i=0; i<height; i++){
    for(j=0; j<width; j++){
      imageTiltShift.at<uchar>(i,j)= (ganhoImage.at<uchar>(i,j)/255.0)*image.at<uchar>(i,j)+(ganhoImage_blurred.at<uchar>(i,j)/255.0)*image_blurred.at<uchar>(i,j);

    }
  }
  imshow("tiltshift",imageTiltShift);
}


void on_trackbar_height(int, void*){
  h =  height_slider*height/100;
  l1 = centro - h/2;
  l2 = centro + h/2;

  if(l1<0){
    l1=0;
  }
  if(l2>height){
    l2=height;
  }

  blend();
}

void on_trackbar_center(int, void*){
  centro=center_slider*height/100;
  l1 = centro - h/2;
  l2 = centro + h/2;

  if(l1<0){
    l1=0;
  }
  if(l2>height){
    l2=height;
  }
  blend();
}

void on_trackbar_decaimento(int, void*){
  //gerar as imagens para combinação a posterior das duas imagens
  d= d_slider;
  blend();
}



int main(int argvc, char** argv){

  image = imread(argv[1],CV_LOAD_IMAGE_GRAYSCALE);

  if(!image.data){
    cout << "imagem nao carregou corretamente\n";
    return(-1);
  }
  //largura e altura da imagem
  width=image.size().width;
  height=image.size().height;

  //criar a imagem borrada
  makeBlurredImage();

  namedWindow("tiltshift", 1);

  //setar a altura
  sprintf( TrackbarName, "Altura: " );
  createTrackbar( TrackbarName, "tiltshift",
          &height_slider,
          height_slider_max,
          on_trackbar_height );
  on_trackbar_height(height_slider, 0 );

  //definir cetro do foco e calcular l1 e l2
  sprintf( TrackbarName, "Centro: ");
  createTrackbar( TrackbarName, "tiltshift",
                                  &center_slider,
                                  center_slider_max,
                                  on_trackbar_center );
  on_trackbar_center(center_slider, 0 );

  //calcula decaimento e junta as 2 imagens
  sprintf( TrackbarName, "Decaimento: " );
  createTrackbar( TrackbarName, "tiltshift",
          &d_slider,
          d_slider_max,
          on_trackbar_decaimento );

  on_trackbar_decaimento(d_slider, 0 );

  waitKey(0);
  return 0;
}
Esse programa é uma modificação do código addweighted.cpp descrito nas atividades da disciplina. Foi gerado o efeito de tiltshift através da combinação de duas imagens, a imagem de entrada e a imagem borrada.
6 1
Figure 19. imagem com o efeito tiltshift
  • video para exemplificação

1.13. Tiltshift Video

  • Utilizando o programa exemplos/addweighted.cpp como referência, implemente um programa tiltshiftvideo.cpp. Tal programa deverá ser capaz de processar um arquivo de vídeo, produzir o efeito de tilt-shift nos quadros presentes e escrever o resultado em outro arquivo de vídeo. A ideia é criar um efeito de miniaturização de cenas. Descarte quadros em uma taxa que julgar conveniente para evidenciar o efeito de stop motion, comum em vídeos desse tipo.

    Para resolver essa questão implementamos o código abaixo:
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;



double d=0;
int l1=0,l2=0,h=0;
int width,height;
int centro=0;

int d_slider = 0;
int d_slider_max = 100;

int center_slider = 0;
int center_slider_max = 100;

int height_slider = 0;
int height_slider_max = 100;

VideoCapture cap;

Mat image;
Mat image_blurred;
Mat ganhoImage;
Mat ganhoImage_blurred;
Mat imageTiltShift;

char TrackbarName[50];

void makeBlurredImage(){
  Mat result, result1;
  int absolut=1;
  image.copyTo(result);

  float media[] = {1,1,1,1,1,1,1,1,1,
           1,1,1,1,1,1,1,1,1,
           1,1,1,1,1,1,1,1,1,
           1,1,1,1,1,1,1,1,1,
           1,1,1,1,1,1,1,1,1,
           1,1,1,1,1,1,1,1,1,
           1,1,1,1,1,1,1,1,1,
           1,1,1,1,1,1,1,1,1,
           1,1,1,1,1,1,1,1,1};
  Mat mask(9,9,CV_32F,media),mask1;

  mask = Mat(9, 9, CV_32F, media);
  scaleAdd(mask, 1/81.0, Mat::zeros(9,9,CV_32F), mask1);
  mask = mask1;

  result.convertTo(result1, CV_32F);
  filter2D(result1, image_blurred, result1.depth(), mask, Point(1,1), 0);
  if(absolut){
      image_blurred=abs(image_blurred);
  }
  image_blurred.convertTo(image_blurred, CV_8U);


}

void blend(){
  int i,j;
  image.copyTo(ganhoImage);
  image.copyTo(ganhoImage_blurred);
  image.copyTo(imageTiltShift);

  //ganho da imagem de entrada
  for(i=0; i<height; i++){
    for(j=0; j<width; j++){
      ganhoImage.at<uchar>(i,j)= 255*(1/2.0)*(tanh((i-l1)/(d*1.0))-tanh((i-l2)/(d*1.0)));
    }
  }

  //ganho da imagem borrada
  for(i=0; i<height; i++){
    for(j=0; j<width; j++){
      ganhoImage_blurred.at<uchar>(i,j)= 255- ganhoImage.at<uchar>(i,j);
    }
  }

  //gerar imagem final como combinação
  for(i=0; i<height; i++){
    for(j=0; j<width; j++){
      imageTiltShift.at<uchar>(i,j)= (ganhoImage.at<uchar>(i,j)/255.0)*image.at<uchar>(i,j)+(ganhoImage_blurred.at<uchar>(i,j)/255.0)*image_blurred.at<uchar>(i,j);

    }
  }
}


void on_trackbar_height(int, void*){
  h =  height_slider*height/100;
  l1 = centro - h/2;
  l2 = centro + h/2;

  if(l1<0){
    l1=0;
  }
  if(l2>height){
    l2=height;
  }

}

void on_trackbar_center(int, void*){
  centro=center_slider*height/100;
  l1 = centro - h/2;
  l2 = centro + h/2;

  if(l1<0){
    l1=0;
  }
  if(l2>height){
    l2=height;
  }
}

void on_trackbar_decaimento(int, void*){
  //gerar as imagens para combinação a posterior das duas imagens
  d= d_slider;
}



int main(int argvc, char** argv){

  cap.open(0);
  if(!cap.isOpened())
    return -1;
  width=cap.get(CV_CAP_PROP_FRAME_WIDTH);
  height=cap.get(CV_CAP_PROP_FRAME_HEIGHT);
  namedWindow("tiltshift", 1);

  while(1){
    cap >> image;
    cvtColor(image, image, CV_BGR2GRAY);
    //criar a imagem borrada
    makeBlurredImage();

    //setar a altura
    sprintf( TrackbarName, "Altura: " );
    createTrackbar( TrackbarName, "tiltshift",
            &height_slider,
            height_slider_max,
            on_trackbar_height );
    on_trackbar_height(height_slider, 0 );

    //definir cetro do foco e calcular l1 e l2
    sprintf( TrackbarName, "Centro: ");
    createTrackbar( TrackbarName, "tiltshift",
                                    &center_slider,
                                    center_slider_max,
                                    on_trackbar_center );
    on_trackbar_center(center_slider, 0 );

    //calcula decaimento e junta as 2 imagens
    sprintf( TrackbarName, "Decaimento: " );
    createTrackbar( TrackbarName, "tiltshift",
            &d_slider,
            d_slider_max,
            on_trackbar_decaimento );

    on_trackbar_decaimento(d_slider, 0 );
    blend();
    imshow("tiltshift",imageTiltShift);
    waitKey(1);

  }
  //waitKey(0);
  return 0;
}
Esse programa é uma modificação do código tiltshift.cpp descrito no exemplo anterior. Foi gerado o efeito de tiltshift para um video.

2. Unidade 2

2.1. Filtragem no Domínio da Frequência

2.2. Filtro Homomórfico

  • Utilizando o programa exemplos/dft.cpp como referência, implemente o filtro homomórfico para melhorar imagens com iluminação irregular. Crie uma cena mal iluminada e ajuste os parâmetros do filtro homomórfico para corrigir a iluminação da melhor forma possível. Assuma que a imagem fornecida é em tons de cinza.

Para resolver essa questão implementamos o código abaixo:

#include <iostream>
#include <math.h>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;



const int MAX=10;
int dft_M, dft_N;

double C,D0,gammaH,gammaL;

int highFrequency_slider = 20;
int highFrequency_slider_max = 50;

int lowFrequency_slider = 2;
int lowFrequency_slider_max = 10;

int smoothness_slider = 1;
int smoothness_slider_max = 100;

int cutoff_slider = 5;
int cutoff_slider_max = 200;

Mat complexImage, tmp,imageFiltered;
Mat padded, filter;
Mat image;
Mat_<float> realInput, zeros;
vector<Mat> planos;

char TrackbarName[50];



// troca os quadrantes da imagem da DFT
void deslocaDFT(Mat& image ){
  Mat aux, A, B, C, D;

  // se a imagem tiver tamanho impar, recorta a regiao para
  // evitar cópias de tamanho desigual
  image = image(Rect(0, 0, image.cols & -2, image.rows & -2));
  int cx = image.cols/2;
  int cy = image.rows/2;

  // reorganiza os quadrantes da transformada
  // A B   ->  D C
  // C D       B A
  A = image(Rect(0, 0, cx, cy));
  B = image(Rect(cx, 0, cx, cy));
  C = image(Rect(0, cy, cx, cy));
  D = image(Rect(cx, cy, cx, cy));

  // A <-> D
  A.copyTo(aux);  D.copyTo(A);  aux.copyTo(D);

  // C <-> B
  C.copyTo(aux);  B.copyTo(C);  aux.copyTo(B);
}


void homomorphicFiltering(){

  // a função de transferência (filtro frequencial) deve ter o
  // mesmo tamanho e tipo da matriz complexa
  filter = Mat(padded.size(), CV_32FC2, Scalar(0));

  // cria uma matriz temporária para criar as componentes real
  // e imaginaria do filtro ideal
  tmp = Mat(dft_M, dft_N, CV_32F);

  // prepara o filtro passa-baixas ideal
  for(int i=0; i<dft_M; i++){
    for(int j=0; j<dft_N; j++){
      tmp.at<float>(i,j)= (gammaH-gammaL)*(1-expf((-1.0*C*(pow(i-dft_M/2.0,2)+pow(j-dft_N/2.0,2)))/(pow(D0,2))))+gammaL;
    }
  }

  // cria a matriz com as componentes do filtro e junta
  // ambas em uma matriz multicanal complexa
  Mat comps[]= {tmp, tmp};
  merge(comps, 2, filter);

}

void applyFilter(void){

  //limpa os planos
  planos.clear();

  // parte imaginaria da matriz complexa (preenchida com zeros)
  zeros = Mat_<float>::zeros(padded.size());

  // prepara a matriz complexa para ser preenchida
  complexImage = Mat(padded.size(), CV_32FC2, Scalar(0));

  // cria a compoente real
  realInput = Mat_<float>(padded);
  //realInput += Scalar::all(1);
  //log(realInput,realInput);

  // insere as duas componentes no array de matrizes
  planos.push_back(realInput);
  planos.push_back(zeros);

  // combina o array de matrizes em uma unica
  // componente complexa
  merge(planos, complexImage);

  // calcula o dft
  dft(complexImage, complexImage);

  // realiza a troca de quadrantes
  deslocaDFT(complexImage);

  //redimensiona e normaliza para remoção de bordas
  resize(complexImage,complexImage,padded.size());
  normalize(complexImage,complexImage,0,1,CV_MINMAX);

  //calcula o filtro homomorfico
  homomorphicFiltering();

  // aplica o filtro frequencial
  mulSpectrums(complexImage,filter,complexImage,0);

  // troca novamente os quadrantes
  deslocaDFT(complexImage);

  // calcula a DFT inversa
  idft(complexImage, complexImage);

  // limpa o array de planos
  planos.clear();

  // separa as partes real e imaginaria da
  // imagem filtrada
  split(complexImage, planos);
  //exp(planos[0],planos[0]);

  // normaliza a parte real para exibicao
  normalize(planos[0], planos[0], 0, 1, CV_MINMAX);

  //imagem filtrada recebe o planos[0]
  imageFiltered = planos[0].clone();

  printf("D0: %f\n", D0);
  printf("C: %f\n", C);
  printf("Alta Frequencia: %f\n", gammaH);
  printf("Baixa Frequencia: %f\n", gammaL);

  imshow("Homomorphic filtering", imageFiltered);
  imshow("Original", image);


}

void on_trackbar_highFrequency(int, void*){
  gammaH =  highFrequency_slider/MAX;
  applyFilter();
}


void on_trackbar_lowFrequency(int, void*){
  gammaL =  lowFrequency_slider/MAX;
  applyFilter();
}


void on_trackbar_smoothness(int, void*){
  C =  smoothness_slider;
  applyFilter();
}


void on_trackbar_cutoff(int, void*){
  D0 =  cutoff_slider;
  applyFilter();
}


int main(int argvc, char** argv){

  //carrega a imagem passada como argumento
  image = imread(argv[1],CV_LOAD_IMAGE_GRAYSCALE);

  if(!image.data){
    cout << "imagem nao carregou corretamente\n";
    return(-1);
  }

   // identifica os tamanhos otimos para
  // calculo do FFT
  dft_M = getOptimalDFTSize(image.rows);
  dft_N = getOptimalDFTSize(image.cols);

  // realiza o padding da imagem
  copyMakeBorder(image, padded, 0,
                 dft_M - image.rows, 0,
                 dft_N - image.cols,
                 BORDER_CONSTANT, Scalar::all(0));

  //prepara a janela para exibição da imagem
  namedWindow("Homomorphic filtering", 1);

  //setar HighFrequency (reflectância)
  sprintf( TrackbarName, "GammaH->High Frequency: " );
  createTrackbar( TrackbarName, "Homomorphic filtering",
          &highFrequency_slider,
          highFrequency_slider_max,
          on_trackbar_highFrequency );
  on_trackbar_highFrequency(highFrequency_slider, 0 );

  //setar LowFrequency (iluminância)
  sprintf( TrackbarName, "GammaL->Low Frequency: " );
  createTrackbar( TrackbarName, "Homomorphic filtering",
          &lowFrequency_slider,
          lowFrequency_slider_max,
          on_trackbar_lowFrequency );
  on_trackbar_lowFrequency(lowFrequency_slider, 0 );

  //setar smoothness (Suavidade da curva do filtro)
  sprintf( TrackbarName, "C->Smoothness of the curve: " );
  createTrackbar( TrackbarName, "Homomorphic filtering",
          &smoothness_slider,
          smoothness_slider_max,
          on_trackbar_smoothness );
  on_trackbar_smoothness(smoothness_slider, 0 );

  //setar cutoff (D0-> frequência de corte -> raio do filtro)
  sprintf( TrackbarName, "D0->Cutoff Frequency: " );
  createTrackbar( TrackbarName, "Homomorphic filtering",
          &cutoff_slider,
          cutoff_slider_max,
          on_trackbar_cutoff );
  on_trackbar_cutoff(cutoff_slider, 0 );

  applyFilter();;
  waitKey(0);
  return 0;
}
Esse programa é uma modificação do código dft.cpp descrito nas atividades da disciplina, um filtro homomórfico consiste em:
7 filtrohomomorfico
Figure 20. explicação de filtro homomórfico
Foi gerado o efeito pedido com ajuste dos parâmetros C, D0, gammaL e gammaH. Sendo o C responsável pela suavidade do filtro, o D0 o raio do filtro, o gammaL corresponde à iluminância(baixas frequências) e o gammaH à reflectância(altas frequências).
A entrada do programa foi:
7 imagemOriginal
Figure 21. entrada do filtro homomórfico
A saída do programa foi:
7 imagemFiltrada
Figure 22. saída do filtro homomórfico
Para exemplificação foi gravado um vídeo:

2.3. Detecção de bordas com o algoritmo de Canny

2.4. Refinamento da Técnica do Pontilhismo

  • Utilizando os programas exemplos/canny.cpp e exemplos/pontilhismo.cpp como referência, implemente um programa cannypoints.cpp. A idéia é usar as bordas produzidas pelo algoritmo de Canny para melhorar a qualidade da imagem pontilhista gerada. A forma como a informação de borda será usada é livre. Entretanto, são apresentadas algumas sugestões de técnicas que poderiam ser utilizadas:

  • Desenhar pontos grandes na imagem pontilhista básica;

  • Usar a posição dos pixels de borda encontrados pelo algoritmo de Canny para desenhar pontos nos respectivos locais na imagem gerada.

  • Experimente ir aumentando os limiares do algoritmo de Canny e, para cada novo par de limiares, desenhar círculos cada vez menores nas posições encontradas. A Figura Pontilhismo aplicado à imagem Lena foi desenvolvida usando essa técnica.

  • Escolha uma imagem de seu gosto e aplique a técnica que você desenvolveu.

  • Descreva no seu relatório detalhes do procedimento usado para criar sua técnica pontilhista.

Para resolver essa questão implementamos o código abaixo:

#include <iostream>
#include <opencv2/opencv.hpp>
#include <fstream>
#include <iomanip>
#include <vector>
#include <algorithm>
#include <numeric>
#include <ctime>
#include <cstdlib>
#define R_inicial 5
#define JITTER 3
#define STEP 10

using namespace std;
using namespace cv;

//esses valores serão modificados conforme aumentarmos os limiares T1 e T2
double RAIO=R_inicial;

Mat image, points;

int width, height;

void pontilhismo(){
  int x,y,gray;
  vector<int> yrange;
  vector<int> xrange;

  xrange.resize(height/STEP);
  yrange.resize(width/STEP);

  iota(xrange.begin(), xrange.end(), 0);
  iota(yrange.begin(), yrange.end(), 0);

  for(uint i=0; i<xrange.size(); i++){
    xrange[i]= xrange[i]*STEP+STEP/2;
  }

  for(uint i=0; i<yrange.size(); i++){
    yrange[i]= yrange[i]*STEP+STEP/2;
  }

  points = Mat(height, width, CV_8U, Scalar(255));

  random_shuffle(xrange.begin(), xrange.end());

  for(auto i : xrange){
    random_shuffle(yrange.begin(), yrange.end());
    for(auto j : yrange){
      x = i+rand()%(2*JITTER)-JITTER+1;
      y = j+rand()%(2*JITTER)-JITTER+1;
      gray = image.at<uchar>(x,y);
      circle(points,
             cv::Point(y,x),
             RAIO,
             CV_RGB(gray,gray,gray),
             -1,
             CV_AA);
    }
  }


}


void canny(){
  int x,y,gray;
  Mat border;
  int i,nIteracoes=10;
  int t2=1;

  for(i=0;i<nIteracoes;i++){
    Canny(image, border, t2, 3*t2);
    t2+=10;

    //refinar pointilhismo
    //para os pontos da borda desenhar circulos menores
    for(x=0;x<height;x++){
      for(y=0;y<width;y++){
        if(border.at<uchar>(x,y)==255){
          gray = image.at<uchar>(x,y);
          circle(points,cv::Point(y,x),RAIO,CV_RGB(gray,gray,gray),-1,CV_AA);
        }
      }
    }
    //diminuir o raio para ponto menor
    RAIO=int(RAIO-R_inicial/(1.0*nIteracoes));

  }

}


int main(int argc, char** argv){
  Mat copiaOriginal,copiaRefinada;
  // guarda tecla capturada
  char key;
  //determinar se quer refinar pontilhismo ou nao
  bool troca=1;
  //carregar imagem
  image= imread(argv[1],CV_LOAD_IMAGE_GRAYSCALE);

  srand(time(0));

  if(!image.data){
        cout << "nao abriu" << argv[1] << endl;
    cout << argv[0] << " imagem.jpg";
    exit(0);
  }


  //altura e largura da imagem
  width=image.size().width;
  height=image.size().height;

  //prepara a janela para exibição da imagem
  namedWindow("Pointilhismo", 1);
  //construção da imagem pointilhista básica
  pontilhismo();
  //guarda copia
  points.copyTo(copiaOriginal);

  //utilizar canny para refinar algoritmo
  canny();
  points.copyTo(copiaRefinada);

  while(1){

    key = (char) waitKey(10);
    if( key == 27 ) break; // esc pressed!
    if(key=='t'){
      if(troca)
        copiaRefinada.copyTo(points);
      else
        copiaOriginal.copyTo(points);

      troca=!troca;
    }
    //resultados
    imshow("Pointilhismo", points);
  }

  imwrite("pontos.jpg", points);
  return 0;
}
Esse programa é uma modificação do código canny.cpp e pontilhismo.cpp descrito nas atividades da disciplina, pontilhismo consiste em uma técnica de pintura e desenho em que as imagens são definidas por pequenas manchas ou pontos.
Foi gerado o efeito pedido através do refinamento dos pontos a serem desenhados. Para tanto, primeiro foi executado o trecho de código do pontilhismo básico (descrito no tutorial) e, a posteriori, é utilizado o algoritmo de canny em um loop com 10 iterações. Para cada iteração alteramos o raio do circulo a ser desenhado (diminuindo-o) e os limiares T1 e T2 que estabelecem a relação de pontos de bordas fortes e fracas da imagem de bordas. Então, percorremos a imagem de bordas e para os pontos que fazem parte da mesma (em branco), desenhamos o círculo com o raio estabelecido para aquela iteração. Além disso apertando a tecla 't' é possível alternar do pontilhismo refinado para o simples.
A entrada do programa foi:
vanessa
Figure 23. entrada do código
A saída do programa foi:
  • Para o código pontilhista simples:

8 pointilhismoNRefinado
Figure 24. saída do pontilhismo apresentado no tutorial
  • Para o código pontilhista Refinado:

8 pointilhismoRefinado
Figure 25. saída do pontilhismo refinado
Além disso, para diversão, foi feito uso de outras imagens, cujas saídas refinadas estão mostradas abaixo:
8 1outros
Figure 26. saída de flores.png
8 3outros
Figure 27. saída de um desenho da abertura de Rick and Morty
8 2outros
Figure 28. saída da minha cachorrinha "Lili"
Para exemplificação foi gravado um vídeo:

2.5. Quantização vetorial com k-means

2.6. K-means com 10 iterações, nRodadas=1 e centros com inicialização aleatória

  • Utilizando o programa kmeans.cpp como exemplo prepare um programa exemplo onde a execução do código se dê usando o parâmetro nRodadas=1 e inciar os centros de forma aleatória usando o parâmetro KMEANS_RANDOM_CENTERS ao invés de KMEANS_PP_CENTERS. Realize 10 rodadas diferentes do algoritmo e compare as imagens produzidas. Explique porque elas podem diferir tanto.

Para resolver essa questão implementamos o código abaixo:

#include <opencv2/opencv.hpp>
#include <cstdlib>
#include <cstring>
using namespace std;
using namespace cv;

int main( int argc, char** argv ){

  int ASCII_NUMBERS ='0';
  int nClusters = 15;
  Mat rotulos;
  int nRodadas = 1; //conforme pedido na questão
  Mat centros;

  if(argc!=3){
        exit(0);
  }

  Mat img = imread( argv[1], CV_LOAD_IMAGE_COLOR);

  //mostrar imagem original
  imshow( "clustered image", img );
  waitKey( 1 );



  for(int i = 0; i < 10; i++){

    Mat samples(img.rows * img.cols, 3, CV_32F);

    for( int y = 0; y < img.rows; y++ ){
      for( int x = 0; x < img.cols; x++ ){
        for( int z = 0; z < 3; z++){
          samples.at<float>(y + x*img.rows, z) = img.at<Vec3b>(y,x)[z];
            }
          }
    }

    kmeans(samples,
                   nClusters,
                   rotulos,
                   TermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS, 10000, 0.0001),
                   nRodadas,
                   KMEANS_RANDOM_CENTERS,//iniciando centros de forma aleatória
                   centros );


    Mat rotulada( img.size(), img.type() );
    for( int y = 0; y < img.rows; y++ ){
      for( int x = 0; x < img.cols; x++ ){
            int indice = rotulos.at<int>(y + x*img.rows,0);
            rotulada.at<Vec3b>(y,x)[0] = (uchar) centros.at<float>(indice, 0);
            rotulada.at<Vec3b>(y,x)[1] = (uchar) centros.at<float>(indice, 1);
            rotulada.at<Vec3b>(y,x)[2] = (uchar) centros.at<float>(indice, 2);
          }
    }
    imshow( "clustered image", rotulada );


    char nome[50]="Saida";


    nome[ strlen(nome) ] = (char)(i + ASCII_NUMBERS);
    nome[ strlen(nome) + 1] = 0x0;

    strcat(nome,argv[1]);

    imwrite(nome, rotulada);

    waitKey( 1 );
  }
}
O algoritmo de K-means aprensentado consiste em uma segmentação de cores. Logo, vejamos um pseudocódigo do algorítmo de K-means:
9 kmeans
Figure 29. pseudocódigo
Esse programa é uma modificação do código kmeans.cpp descrito nas atividades da disciplina. Para o programa, utilizamos um loop com 10 iterações, em cada iteração os centros do algoritmo de kmeans eram inicializados aleatoriamente, diferentemente para cada loop.
As entradas do programa foram:
mandala1
Figure 30. entrada mandala1.png
mandala2
Figure 31. entrada mandala2.png
sobremesa
Figure 32. entrada sobremesa.png
A saída do programa pode ser vista no video para exemplificação:
  • video para exemplificação https://youtu.be/gD3hcCqJxkw

    Como podemos perceber a imagem gerada difere para cada inicialização de centros. O algoritmo escolhe centros que irão definir quais cores serão utilizadas para representar a imagem final (no nosso algoritmo definimos 15 cores). Desse modo, gerando os centros de maneira aleatória, o resultado final para cada centro é diferente, por isso a imagem varia a cada iteração.

3. Unidade 3

3.1. Trabalho Final

Acessar via Trabalho Final